package com.love.rongapi.aop;

import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.love.rongapi.constant.ApiEnum;
import com.love.rongapi.model.Log;
import com.love.rongapi.service.LogService;
import com.love.rongcommon.constant.AppKey;
import com.love.rongcommon.jwt.JwtUtil;
import com.love.rongcommon.vo.AccessTokenVo;
import io.swagger.annotations.ApiImplicitParam;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Slf4j
@Aspect
@Configuration
public class WebAspect {
    private static final ThreadLocal<Long> START_TIME_MILLIS = new ThreadLocal<>();

    @Resource
    private HttpServletRequest request;

    @Resource(name = "stringRedisTemplate_0")
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private LogService logService;

    /**
     * 请求日志切入点
     */
    @Pointcut("execution(public * com.love.rongapi.controller..*.*(..))")
    public void logPointcut(){}

    /**
     * 前置通知
     */
    @Before("logPointcut()")
    public void before(JoinPoint joinPoint) {
        // 记录当前时间
        START_TIME_MILLIS.set(System.currentTimeMillis());
    }

    /**
     * 验证token
     */
    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //1.记录日志信息
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        Long userId = 0L;
        if (targetMethod.isAnnotationPresent(ApiImplicitParam.class)) {
            ApiImplicitParam annotation = targetMethod.getAnnotation(ApiImplicitParam.class);
            if (AppKey.ACCESS_TOKEN_KEY.equals(annotation.name())) {
                // accessToken校验（uuid32位作为accessToken）
                String accessToken = request.getHeader(AppKey.ACCESS_TOKEN_KEY);
                if (ObjectUtil.isNotEmpty(accessToken)) {
                    // accessToken解密
                    AccessTokenVo accessTokenVo = JwtUtil.decode(accessToken);

                    // 获取redis
                    String key = StrFormatter.format("{}:{}", AppKey.ACCESS_TOKEN_KEY, accessTokenVo.getUserId());
                    String value = stringRedisTemplate.opsForValue().get(key);
                    ApiEnum.TOKEN_NOT_EXISTS.isNotEmpty(value);

                    // 校验accessToken
                    AccessTokenVo vo = JSONUtil.toBean(value, AccessTokenVo.class);
                    ApiEnum.TOKEN_ERROR.isTrue(accessToken.equals(vo.getAccessToken()));
                    userId = vo.getUserId();
                } else {
                    ApiEnum.TOKEN_NOT_EXISTS.isThrow();
                }
            }
        }
        Object result = joinPoint.proceed();
        serviceLog(joinPoint, userId, result);

        return result;
    }

    private void serviceLog(JoinPoint joinPoint, Long userId, Object result) {
        try {
            Log logger = new Log();
            logger.setUserId(userId);
            logger.setUrl(request.getRequestURL().toString());
            logger.setHeader(request.getHeader(AppKey.ACCESS_TOKEN_KEY));
            logger.setParam(JSONUtil.toJsonStr(joinPoint.getArgs()));
            logger.setIp(request.getRemoteAddr());
            logger.setClassName(joinPoint.getSignature().getDeclaringTypeName());
            logger.setMethod(joinPoint.getSignature().getName());
            logger.setResult(isBaseType(result) ? String.valueOf(result) : JSONUtil.toJsonStr(result));
            logger.setTimes(System.currentTimeMillis() - START_TIME_MILLIS.get());
            logService.saveLog(logger);

            log.info(JSONUtil.toJsonStr(logger));

            START_TIME_MILLIS.remove();
        } catch (Exception e) {
            log.error("serviceLog", e);
        }
    }


    /**
     * 异常通知
     *
     * @param joinPoint 参数
     */
    @AfterThrowing(value = "logPointcut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        serviceLog(joinPoint, 0L, ex.getMessage());
        START_TIME_MILLIS.remove();
    }

    private boolean isBaseType(Object body) {
        return body instanceof String ||
                body instanceof Integer ||
                body instanceof Boolean ||
                body instanceof Byte ||
                body instanceof Long ||
                body instanceof Double ||
                body instanceof Float ||
                body instanceof Short;
    }
}
